package com.n.passwordbook.Utils;

import javafx.animation.AnimationTimer;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.paint.Color;
import javafx.scene.shape.ArcType;

import java.util.Arrays;
import java.util.Comparator;
import java.util.LinkedList;
import java.util.Random;

public class ParticleSystem
{
    class ParticleAnimationTimer extends AnimationTimer
    {
        public static final double SHOW_LINE_DISTANCE_SQUARE = 3600.0;
        private static final double NEAR_COEFFICIENT = 0.052;
        private static final double MOUSE_ACCELERATION_ADJUST_COEFFICIENT = 0.01586468;
        private static final double MOUSE_MOVE_AFFECT_DISTANCE_SQUARE = 4900;
        private static final double MOUSE_MOVE_AFFECT_DURATION = 140.0;
        private int flag = 0;

        @Override
        public void handle(long l)
        {
            if (flag < 1)
            {
                flag++;
                return;
            }
            flag = 0;
            double currentMouseX = mouseX;
            double currentMouseY = mouseY;

            //清屏并设置默认填充颜色
            graphicsContext.clearRect(0.0, 0.0, maxWidth, maxHeight);
            graphicsContext.setFill(color);
            //预计算中心到坐标偏差值
            final double halfRadius = radius / 2;
            for (int i = 0; i < particles.length; i++)
            {
                //获得当前点的xy坐标（位于图像左上角）
                double currentX = particles[i].getX();
                double currentY = particles[i].getY();
                //于坐标处画图
                graphicsContext.setFill(color);
                graphicsContext.fillArc(currentX, currentY, radius, radius, 0, 360, ArcType.ROUND);
                //graphicsContext.fillText(String.valueOf(i), currentX, currentY);//调试时用于显示粒子序号

                //将xy坐标变换到图像中心
                currentX += halfRadius;
                currentY += halfRadius;

                //创建相连粒子坐标数据
                var trianglesDesc = new TrianglesDesc(currentX, currentY, color);
                //记录粒子相连数据
                for (int j = 0; j < i; j++)
                {
                    //创建搜索目标的xy图像中心坐标
                    double searchedX = particles[j].getX() + halfRadius;
                    double searchedY = particles[j].getY() + halfRadius;
                    //计算图像中心距离差
                    double distanceSquare = Math.pow(searchedX - currentX, 2.0) + Math.pow(searchedY - currentY, 2.0);
                    if (distanceSquare < SHOW_LINE_DISTANCE_SQUARE)
                    {
                        double alpha = 1 - (distanceSquare / SHOW_LINE_DISTANCE_SQUARE);
                        trianglesDesc.addTargetPoint(particles[j].getX() + 1, particles[j].getY() + 1, alpha);
                    }
                }
                //记录并绘制粒子相连图像
                for (int j = i + 1; j < particles.length; j++)
                {
                    //创建搜索目标的xy图像中心坐标
                    double searchedX = particles[j].getX() + halfRadius;
                    double searchedY = particles[j].getY() + halfRadius;
                    //计算图像中心距离差
                    double xDistance = searchedX - currentX;
                    double yDistance = searchedY - currentY;
                    double distanceSquare = Math.pow(xDistance, 2.0) + Math.pow(yDistance, 2.0);
                    if (distanceSquare < SHOW_LINE_DISTANCE_SQUARE)
                    {
                        //计算图像之间距离平方与最大距离平方的比值并用1减
                        double alpha = 1 - (distanceSquare / SHOW_LINE_DISTANCE_SQUARE);
                        trianglesDesc.addTargetPoint(searchedX, searchedY, alpha);
                        //获得图像距离
                        double distance = Math.sqrt(distanceSquare);
                        //计算xy方向加速度
                        double aX = NEAR_COEFFICIENT * (xDistance / distance) * alpha;
                        double aY = NEAR_COEFFICIENT * (yDistance / distance) * alpha;
                        //当前粒子在xy方向上移动
                        particles[i].moveEx(aX, aY);

                        //反向加速度
                        aX *= -1;
                        aY *= -1;
                        //被搜索粒子在xy方向上移动
                        particles[j].moveEx(aX, aY);
                        //设置连线颜色透明度并将当前粒子与被搜索粒子连线
                        graphicsContext.setStroke(new Color(color.getRed(), color.getGreen(), color.getBlue(), alpha));
                        graphicsContext.strokeLine(currentX, currentY, searchedX, searchedY);
                    }
                }
                //填充粒子相连后的三角形区域
                if (trianglesDesc.getSize() > 1)//数量足以组成三角形时绘图
                {
                    trianglesDesc.drawTriangles(graphicsContext);
                }
            }

            //计算鼠标加速度，方向为过去点指向当前点（即与鼠标运动轨迹相同）
            final double deltaMouseX = (lastMouseX - currentMouseX);
            final double deltaMouseY = (lastMouseY - currentMouseY);
            //假设变化时间为1，以此计算鼠标移动速度
            final double mouseSpeed = Math.sqrt(Math.pow(deltaMouseX, 2.0) + Math.pow(deltaMouseY, 2.0)) * MOUSE_ACCELERATION_ADJUST_COEFFICIENT;
            boolean mouseMove = true;
            //若两个变化量均为0，则说明鼠标没有移动过
            if (deltaMouseX == 0 && deltaMouseY == 0)
            {
                mouseMove = false;
            }

            //若鼠标移动过则更新粒子位置数据
            if (mouseMove)
            {
                Arrays.stream(particles).parallel().forEach(particle -> {
                    //计算鼠标和粒子在xy方向距离和当前鼠标位置和粒子的距离
                    double xDistance = (currentMouseX - particle.getX()) + halfRadius;
                    double yDistance = (currentMouseY - particle.getY()) + halfRadius;
                    double distanceSquare = Math.pow(xDistance, 2.0) + Math.pow(yDistance, 2.0);
                    if (distanceSquare < MOUSE_MOVE_AFFECT_DISTANCE_SQUARE)
                    {
                        //由鼠标距离和最大距离求得比值作为鼠标对粒子施力的衰减系数
                        double reduceCoefficient = 1 - (distanceSquare / MOUSE_MOVE_AFFECT_DISTANCE_SQUARE) * 2;
                        double distance = Math.sqrt(distanceSquare);
                        double aX = (mouseSpeed) * (xDistance / distance) * reduceCoefficient;
                        double aY = (mouseSpeed) * (yDistance / distance) * reduceCoefficient;
                        //将计算得到的xy两方向加速度添加到粒子本身的加速度列表中
                        particle.getLifeTimeLimitedAccelerations().add(new LifeTimeLimitedAcceleration(aX, aY, MOUSE_MOVE_AFFECT_DURATION));
                    }
                    particle.move();
                });
            }
            else
            {
                //并行流统一移动粒子
                Arrays.stream(particles).parallel().forEach(Particle::move);
            }
            //将当前鼠标位置作为上一次的鼠标位置
            lastMouseX = currentMouseX;
            lastMouseY = currentMouseY;
        }
    }

    private final Particle[] particles;
    private final GraphicsContext graphicsContext;
    private final double maxWidth;
    private final double maxHeight;
    private final Color color;
    private final double radius;
    private double lastMouseX;
    private double lastMouseY;
    public volatile double mouseX;
    public volatile double mouseY;

    private final ParticleAnimationTimer animationTimer;

    public ParticleSystem(Canvas targetCanvas, int size, Color color, double radius)
    {
        var random = new Random();
        maxWidth = targetCanvas.getWidth();
        maxHeight = targetCanvas.getHeight();
        graphicsContext = targetCanvas.getGraphicsContext2D();
        graphicsContext.setLineWidth(1.0);

        particles = new Particle[size];
        for (int i = 0; i < size; i++)
        {
            particles[i] = new Particle(random.nextInt(Math.toIntExact(Math.round(maxWidth))),
                    random.nextInt(Math.toIntExact(Math.round(maxHeight))),
                    maxWidth, maxHeight);
        }
        this.color = color;
        this.radius = radius;
        animationTimer = new ParticleAnimationTimer();
    }

    public void start()
    {
        animationTimer.start();
    }

    public void stop()
    {
        animationTimer.stop();
    }

    public void playOnce()
    {
        animationTimer.handle(0);
    }
}

class Particle
{
    private static final double ACCELERATED_COEFFICIENT = 1.13;

    private double X;
    private double Y;
    private double dX;
    private double dY;

    private final double maxWidth;
    private final double maxHeight;

    private final LinkedList<LifeTimeLimitedAcceleration> lifeTimeLimitedAccelerations;

    Particle(double x, double y, double maxWidth, double maxHeight)
    {
        X = x;
        Y = y;
        this.maxWidth = maxWidth;
        this.maxHeight = maxHeight;
        var random = new Random();
        dX = random.nextBoolean() ?
                random.nextDouble() * ACCELERATED_COEFFICIENT : (-1) * random.nextDouble() * ACCELERATED_COEFFICIENT;
        dY = random.nextBoolean() ?
                random.nextDouble() * ACCELERATED_COEFFICIENT : (-1) * random.nextDouble() * ACCELERATED_COEFFICIENT;
        lifeTimeLimitedAccelerations = new LinkedList<>();
    }

    public void move()
    {
        for (var it = lifeTimeLimitedAccelerations.iterator(); it.hasNext(); )
        {
            var lifeTimeLimitedAcceleration = it.next();
            X += lifeTimeLimitedAcceleration.getDeltaX();
            Y += lifeTimeLimitedAcceleration.getDeltaY();
            lifeTimeLimitedAcceleration.reduceLife();
        }

        for (var reverseIt = lifeTimeLimitedAccelerations.descendingIterator(); reverseIt.hasNext(); )
        {
            var lifeTimeLimitedAcceleration = reverseIt.next();
            if (lifeTimeLimitedAcceleration.checkLifeTime())
            {
                reverseIt.remove();
            }
            else
            {
                break;
            }
        }

        X += dX;
        Y += dY;
        //超出区域则反转全部加速度
        if (X > maxWidth || X < 0)
        {
            dX *= (-1);
            for (var lifeTimeLimitedAcceleration : lifeTimeLimitedAccelerations)
            {
                lifeTimeLimitedAcceleration.reverseDeltaX();
            }
        }
        if (Y > maxHeight || Y < 0)
        {
            dY *= (-1);
            for (var lifeTimeLimitedAcceleration : lifeTimeLimitedAccelerations)
            {
                lifeTimeLimitedAcceleration.reverseDeltaY();
            }
        }
    }

    public void moveEx(double dx, double dy)
    {
        X += dx;
        Y += dy;
        double differX = X - maxWidth;
        if (differX > 0)
        {
            X -= differX + differX;
        }
        else if (X < 0)
        {
            X -= X + X;
        }
        double differY = Y - maxHeight;
        if (differY > 0)
        {
            Y -= differY + differY;
        }
        else if (Y < 0)
        {
            Y -= Y + Y;
        }
    }

    public double getX()
    {
        return X;
    }

    public double getY()
    {
        return Y;
    }

    public LinkedList<LifeTimeLimitedAcceleration> getLifeTimeLimitedAccelerations()
    {
        return lifeTimeLimitedAccelerations;
    }
}

class TrianglesDesc
{
    private static double COLOR_ALPHA_ADJUST_COEFFICIENT = 2.2;
    private final LinkedList<ParticlePoint> PointsToLine;
    private final double centerX;
    private final double centerY;
    private final Color color;

    public TrianglesDesc(double centerX, double centreY, Color color)
    {
        this.centerX = centerX;
        this.centerY = centreY;
        this.color = color;
        PointsToLine = new LinkedList<>();
    }

    public void addTargetPoint(double x, double y, double alpha)
    {
        PointsToLine.add(new ParticlePoint(x, y, alpha, Math.atan(x - centerX / y - centerY)));
    }

    public int getSize()
    {
        return PointsToLine.size();
    }

    public void drawTriangles(GraphicsContext graphicsContext)
    {
        PointsToLine.sort(Comparator.comparingDouble(ParticlePoint::getDegree));
        var itNext = PointsToLine.iterator();
        itNext.next();
        ParticlePoint currentPoint;
        ParticlePoint nextPoint;
        double alphaThird;
        double targetAlpha;
        var triangleXs = new double[3];
        var triangleYs = new double[3];
        triangleXs[0] = centerX;
        triangleYs[0] = centerY;
        double distanceSquare;

        for (var it = PointsToLine.iterator(); itNext.hasNext(); )
        {
            currentPoint = it.next();
            nextPoint = itNext.next();
            distanceSquare = Math.pow(currentPoint.getX() - nextPoint.getX(), 2.0) + Math.pow(currentPoint.getY() - nextPoint.getY(), 2.0);
            if (distanceSquare < ParticleSystem.ParticleAnimationTimer.SHOW_LINE_DISTANCE_SQUARE)
            {
                triangleXs[1] = currentPoint.getX();
                triangleYs[1] = currentPoint.getY();
                triangleXs[2] = nextPoint.getX();
                triangleYs[2] = nextPoint.getY();

                alphaThird = distanceSquare / ParticleSystem.ParticleAnimationTimer.SHOW_LINE_DISTANCE_SQUARE;
                targetAlpha = (currentPoint.getAlpha() + nextPoint.getAlpha() + alphaThird) / 3 / COLOR_ALPHA_ADJUST_COEFFICIENT;
                graphicsContext.setFill(new Color(color.getRed(), color.getGreen(), color.getBlue(), targetAlpha));
                graphicsContext.fillPolygon(triangleXs, triangleYs, 3);
            }
        }
        currentPoint = PointsToLine.getFirst();
        nextPoint = PointsToLine.getLast();
        distanceSquare = Math.pow(currentPoint.getX() - 1 - nextPoint.getX(), 2.0) + Math.pow(currentPoint.getY() - 1 - nextPoint.getY(), 2.0);
        if (distanceSquare < ParticleSystem.ParticleAnimationTimer.SHOW_LINE_DISTANCE_SQUARE)
        {
            triangleXs[1] = currentPoint.getX();
            triangleYs[1] = currentPoint.getY();
            triangleXs[2] = nextPoint.getX();
            triangleYs[2] = nextPoint.getY();

            alphaThird = distanceSquare / ParticleSystem.ParticleAnimationTimer.SHOW_LINE_DISTANCE_SQUARE;
            targetAlpha = (currentPoint.getAlpha() + nextPoint.getAlpha() + alphaThird) / 3 / COLOR_ALPHA_ADJUST_COEFFICIENT;
            graphicsContext.setFill(new Color(color.getRed(), color.getGreen(), color.getBlue(), targetAlpha));
            graphicsContext.fillPolygon(triangleXs, triangleYs, 3);
        }
    }
}

class LifeTimeLimitedAcceleration
{
    private double lifeTime;
    private final double maxLifeTime;
    private double dx;
    private double dy;
    private double lifeRatio;

    public LifeTimeLimitedAcceleration(double dx, double dy, double lifeTime)
    {
        this.lifeTime = lifeTime;
        maxLifeTime = lifeTime;
        this.dx = dx;
        this.dy = dy;
        lifeRatio = 1.0;
    }

    public void reduceLife()
    {
        lifeTime--;
        if (lifeTime > 1)
        {
            lifeRatio = lifeTime / maxLifeTime;
        }
    }

    public boolean checkLifeTime()
    {
        return lifeTime <= 1;
    }

    public double getDeltaX()
    {
        return dx * lifeRatio;
    }

    public double getDeltaY()
    {
        return dy * lifeRatio;
    }

    public void reverseDeltaX()
    {
        dx *= -1;
    }

    public void reverseDeltaY()
    {
        dy *= -1;
    }
}

class ParticlePoint
{
    private final double x;
    private final double y;
    private final double alpha;
    private final double degree;

    ParticlePoint(double x, double y, double alpha, double degree)
    {
        this.x = x;
        this.y = y;
        this.alpha = alpha;
        this.degree = degree;
    }

    public double getX()
    {
        return x;
    }

    public double getY()
    {
        return y;
    }

    public double getDegree()
    {
        return degree;
    }

    public double getAlpha()
    {
        return alpha;
    }
}